/*
 * SoftwareSAR - 1.0
 */
package br.com.cpqd.ssar.importadores;

import br.com.cpqd.ssar.utilitarias.Geometria;
import br.com.cpqd.ssar.utilitarias.STL;
import br.com.cpqd.ssar.utilitarias.Triangulo;
import br.com.cpqd.ssar.utilitarias.Vertice;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

/**
 * Importador STL
 * @author Rafael Buck
 * @version 1.0
 */
public class ImportadorSTL {

    /**
     * Variáveis de leitura
     */
    private String STR_SOLID = new String("solid");
    private String STR_ENDSOLID = new String("endsolid");
    private String STR_FACET = new String("facet");
    private String STR_ENDFACET = new String("endfacet");
    private String STR_LOOP = new String("loop");
    private String STR_ENDLOOP = new String("endloop");
    private String STR_NORMAL = new String("normal");
    private String STR_OUTER = new String("outer");
    private String STR_VERTEX = new String("vertex");

    public Geometria carregarGeometria(String caminho, int id) {

        int i = 0;
        int j = 0;
        boolean stlBinary = true;           // Indica que o Arquivo lido esta em STL binario
        FileInputStream is;                 // Stream de Entrada
        BufferedReader br;                  // Buffer de leitura
        DataInputStream dis;
        String s;                           // String que representa a linha do arquivo lida
        String substring;                   // String auxiliar usada no StringTokenizer

        Vertice[] vertices = new Vertice[3];  // Criacao de vertices de um elemento da malha
        float[] normal = new float[3];      // Criacao da normal de um elemento da malha

        Triangulo[] malha = null;            // Lista de Triangulos

        float coordinate;                   // Usado na comparacao de valores de vertices (max e min)

        float xmin = Float.MAX_VALUE, xmax = Float.MIN_VALUE;
        float ymin = Float.MAX_VALUE, ymax = Float.MIN_VALUE;
        float zmin = Float.MAX_VALUE, zmax = Float.MIN_VALUE;

        try {

            // Testa SLT para ver se ele e ASCII ou Binario
            is = new FileInputStream(caminho);
            // Evita -deprecation devido ao método readLine()
            br = new BufferedReader(new InputStreamReader(is));
            // Varre o arquivo linha por linha
            if ((s = br.readLine()) != null) {
                StringTokenizer st = new StringTokenizer(s);
                substring = st.nextToken();
                if (substring.compareTo(STR_SOLID) == 0) {
                    stlBinary = false;

                    while ((s = br.readLine()) != null) {
                        st = new StringTokenizer(s);
                        while (st.hasMoreTokens()) {
                            substring = st.nextToken();
                            if (substring.compareTo(STR_FACET) == 0) {
                                i++;
                            }
                        }
                    }
                    System.out.println(i + "Elementos");
                    malha = new Triangulo[i];
                    i = 0;

                } else {
                    stlBinary = true;
                }
            }
            is.close();

            // Le STL ASCII
            if (stlBinary == false) {
                is = new FileInputStream(caminho);
                // Evita -deprecation devido ao método readLine()
                br = new BufferedReader(new InputStreamReader(is));
                // Varre o arquivo linha por linha
                while ((s = br.readLine()) != null) {
                    StringTokenizer st = new StringTokenizer(s);
                    while (st.hasMoreTokens()) {
                        substring = st.nextToken();
                        if (substring.compareTo(STR_SOLID) == 0) {
                            // Descarta o Nome do Sólido
                            if (st.hasMoreTokens()) {
                                st.nextToken();
                            }
                        } else if (substring.compareTo(STR_ENDSOLID) == 0) {
                            // Descarta "endsolid" e comeca a varrer o proximo solido (se houver)
                            // Descarta o Nome do Sólido
                            if (st.hasMoreTokens()) {
                                st.nextToken();
                            }
//                            System.out.println("Carregado");
                        } else if (substring.compareTo(STR_FACET) == 0) {
                            if (st.hasMoreTokens()) {
                                if (st.nextToken().compareTo(STR_NORMAL) == 0) {
                                    // Associa as coordenadas
                                    normal = new float[3];
                                    normal[0] = Float.valueOf(st.nextToken());
                                    normal[1] = Float.valueOf(st.nextToken());
                                    normal[2] = Float.valueOf(st.nextToken());
                                }
                            }
                        } else if (substring.compareTo(STR_OUTER) == 0) {
                            // Descarta "loop"
                            if (st.hasMoreTokens()) {
                                if (st.nextToken().compareTo(STR_LOOP) == 0) {
                                    vertices = new Vertice[3];
                                    vertices[0] = new Vertice();
                                    vertices[1] = new Vertice();
                                    vertices[2] = new Vertice();
                                    j = 0;
                                }
                            }
                        } else if (substring.compareTo(STR_VERTEX) == 0) {
                            // Associa as coordenadas
                            vertices[j].setCoordenada(0, Float.valueOf(st.nextToken()));
                            vertices[j].setCoordenada(1, Float.valueOf(st.nextToken()));
                            vertices[j].setCoordenada(2, Float.valueOf(st.nextToken()));

                            //************** Coordinates max and min ***************
                            coordinate = vertices[j].getCoordenada(0);
                            if (coordinate > xmax) {
                                xmax = coordinate;
                            } else if (coordinate < xmin) {
                                xmin = coordinate;
                            }
                            coordinate = vertices[j].getCoordenada(1);
                            if (coordinate > ymax) {
                                ymax = coordinate;
                            } else if (coordinate < ymin) {
                                ymin = coordinate;
                            }
                            coordinate = vertices[j].getCoordenada(2);
                            if (coordinate > zmax) {
                                zmax = coordinate;
                            } else if (coordinate < zmin) {
                                zmin = coordinate;
                            }
                            //******************************************************

                            j++;
                        } else if (substring.compareTo(STR_ENDLOOP) == 0) {
                            if (j < 3) {
                                throw new Exception(" Faceta com Menos de Três Vértices ");
                            }
                            malha[i] = new Triangulo(vertices, normal);
                            i++;
                        } else if (substring.compareTo(STR_ENDFACET) == 0) {
                            // Descarta "endfacet"
                        } else {
                            //throw new Exception(" Palavra-chave inválida ");
                            return null;
                        }
                    // Compara se st possui mais tokens
                    }
                }
                is.close();

                System.out.println("Carregado");

            }

            // Le STL Binario
            if (stlBinary == true) {

                is = new FileInputStream(caminho);
                dis = new DataInputStream(is);

                for (int k = 0; k < 80; k++) {
                    System.out.print((char) dis.readByte());
                }
                System.out.println();
                byte[] buffer = new byte[4];
                buffer[0] = (byte) dis.readUnsignedByte();
                buffer[1] = (byte) dis.readUnsignedByte();
                buffer[2] = (byte) dis.readUnsignedByte();
                buffer[3] = (byte) dis.readUnsignedByte();
                i = byteArrayToInt(buffer);
                System.out.println(i + " Elementos");
                malha = new Triangulo[i];

                vertices[0] = new Vertice();
                vertices[1] = new Vertice();
                vertices[2] = new Vertice();

                for (int k = 0; k < i; k++) {

                    normal = new float[3];

                    buffer[0] = (byte) dis.readUnsignedByte();
                    buffer[1] = (byte) dis.readUnsignedByte();
                    buffer[2] = (byte) dis.readUnsignedByte();
                    buffer[3] = (byte) dis.readUnsignedByte();
                    normal[0] = byteArrayToFloat(buffer);

                    buffer[0] = (byte) dis.readUnsignedByte();
                    buffer[1] = (byte) dis.readUnsignedByte();
                    buffer[2] = (byte) dis.readUnsignedByte();
                    buffer[3] = (byte) dis.readUnsignedByte();
                    normal[1] = byteArrayToFloat(buffer);

                    buffer[0] = (byte) dis.readUnsignedByte();
                    buffer[1] = (byte) dis.readUnsignedByte();
                    buffer[2] = (byte) dis.readUnsignedByte();
                    buffer[3] = (byte) dis.readUnsignedByte();
                    normal[2] = byteArrayToFloat(buffer);

//                    System.out.print("i= " + normal[0]);
//                    System.out.print(" j= " + normal[1]);
//                    System.out.println(" k= " + normal[2]);

                    vertices = new Vertice[3];
                    vertices[0] = new Vertice();
                    vertices[1] = new Vertice();
                    vertices[2] = new Vertice();

                    buffer[0] = (byte) dis.readUnsignedByte();
                    buffer[1] = (byte) dis.readUnsignedByte();
                    buffer[2] = (byte) dis.readUnsignedByte();
                    buffer[3] = (byte) dis.readUnsignedByte();
                    vertices[0].setCoordenada(0, byteArrayToFloat(buffer));

                    buffer[0] = (byte) dis.readUnsignedByte();
                    buffer[1] = (byte) dis.readUnsignedByte();
                    buffer[2] = (byte) dis.readUnsignedByte();
                    buffer[3] = (byte) dis.readUnsignedByte();
                    vertices[0].setCoordenada(1, byteArrayToFloat(buffer));

                    buffer[0] = (byte) dis.readUnsignedByte();
                    buffer[1] = (byte) dis.readUnsignedByte();
                    buffer[2] = (byte) dis.readUnsignedByte();
                    buffer[3] = (byte) dis.readUnsignedByte();
                    vertices[0].setCoordenada(2, byteArrayToFloat(buffer));

//                    System.out.print("x= " + vertices[0].getX());
//                    System.out.print(" y= " + vertices[0].getY());
//                    System.out.println(" z= " + vertices[0].getZ());

                    //************** Coordinates max and min ***************
                    coordinate = vertices[0].getCoordenada(0);
                    if (coordinate > xmax) {
                        xmax = coordinate;
                    } else if (coordinate < xmin) {
                        xmin = coordinate;
                    }
                    coordinate = vertices[0].getCoordenada(1);
                    if (coordinate > ymax) {
                        ymax = coordinate;
                    } else if (coordinate < ymin) {
                        ymin = coordinate;
                    }
                    coordinate = vertices[0].getCoordenada(2);
                    if (coordinate > zmax) {
                        zmax = coordinate;
                    } else if (coordinate < zmin) {
                        zmin = coordinate;
                    }
                    //******************************************************

                    buffer[0] = (byte) dis.readUnsignedByte();
                    buffer[1] = (byte) dis.readUnsignedByte();
                    buffer[2] = (byte) dis.readUnsignedByte();
                    buffer[3] = (byte) dis.readUnsignedByte();
                    vertices[1].setCoordenada(0, byteArrayToFloat(buffer));

                    buffer[0] = (byte) dis.readUnsignedByte();
                    buffer[1] = (byte) dis.readUnsignedByte();
                    buffer[2] = (byte) dis.readUnsignedByte();
                    buffer[3] = (byte) dis.readUnsignedByte();
                    vertices[1].setCoordenada(1, byteArrayToFloat(buffer));

                    buffer[0] = (byte) dis.readUnsignedByte();
                    buffer[1] = (byte) dis.readUnsignedByte();
                    buffer[2] = (byte) dis.readUnsignedByte();
                    buffer[3] = (byte) dis.readUnsignedByte();
                    vertices[1].setCoordenada(2, byteArrayToFloat(buffer));

//                    System.out.print("x= " + vertices[1].getX());
//                    System.out.print(" y= " + vertices[1].getY());
//                    System.out.println(" z= " + vertices[1].getZ());

                    //************** Coordinates max and min ***************
                    coordinate = vertices[1].getCoordenada(0);
                    if (coordinate > xmax) {
                        xmax = coordinate;
                    } else if (coordinate < xmin) {
                        xmin = coordinate;
                    }
                    coordinate = vertices[1].getCoordenada(1);
                    if (coordinate > ymax) {
                        ymax = coordinate;
                    } else if (coordinate < ymin) {
                        ymin = coordinate;
                    }
                    coordinate = vertices[1].getCoordenada(2);
                    if (coordinate > zmax) {
                        zmax = coordinate;
                    } else if (coordinate < zmin) {
                        zmin = coordinate;
                    }
                    //******************************************************

                    buffer[0] = (byte) dis.readUnsignedByte();
                    buffer[1] = (byte) dis.readUnsignedByte();
                    buffer[2] = (byte) dis.readUnsignedByte();
                    buffer[3] = (byte) dis.readUnsignedByte();
                    vertices[2].setCoordenada(0, byteArrayToFloat(buffer));

                    buffer[0] = (byte) dis.readUnsignedByte();
                    buffer[1] = (byte) dis.readUnsignedByte();
                    buffer[2] = (byte) dis.readUnsignedByte();
                    buffer[3] = (byte) dis.readUnsignedByte();
                    vertices[2].setCoordenada(1, byteArrayToFloat(buffer));

                    buffer[0] = (byte) dis.readUnsignedByte();
                    buffer[1] = (byte) dis.readUnsignedByte();
                    buffer[2] = (byte) dis.readUnsignedByte();
                    buffer[3] = (byte) dis.readUnsignedByte();
                    vertices[2].setCoordenada(2, byteArrayToFloat(buffer));

//                    System.out.print("x= " + vertices[2].getX());
//                    System.out.print(" y= " + vertices[2].getY());
//                    System.out.println(" z= " + vertices[2].getZ());

                    //************** Coordinates max and min ***************
                    coordinate = vertices[2].getCoordenada(0);
                    if (coordinate > xmax) {
                        xmax = coordinate;
                    } else if (coordinate < xmin) {
                        xmin = coordinate;
                    }
                    coordinate = vertices[2].getCoordenada(1);
                    if (coordinate > ymax) {
                        ymax = coordinate;
                    } else if (coordinate < ymin) {
                        ymin = coordinate;
                    }
                    coordinate = vertices[2].getCoordenada(2);
                    if (coordinate > zmax) {
                        zmax = coordinate;
                    } else if (coordinate < zmin) {
                        zmin = coordinate;
                    }
                    //******************************************************

                    malha[k] = new Triangulo(vertices, normal);

                    dis.readByte();
                    dis.readByte();
                }

                System.out.println("Carregado");
                is.close();
            }

//            System.out.println("minimos |" + " x = " + xmin + " y = " + ymin + " z = " + zmin);
//            System.out.println("maximos |" + " x = " + xmax + " y = " + ymax + " z = " + zmax);

            float ajustex=0, ajustey=0, ajustez=0;
            if (xmin<0)
                ajustex = Math.abs(xmin);
            if (ymin<0)
                ajustey = Math.abs(ymin);
            if (zmin<0)
                ajustez = Math.abs(zmin);

            if ((ajustex > 0) || (ajustey > 0) || (ajustez > 0)) {

                for (int cont = 0; cont < i; cont++) {

                    // Retorna o vertice da posicao corrente na malha
                    vertices = malha[cont].getVertice();

                    // Primeiro Vertice
                    vertices[0].setCoordenada(0, vertices[0].getCoordenada(0) + ajustex);
                    vertices[0].setCoordenada(1, vertices[0].getCoordenada(1) + ajustey);
                    vertices[0].setCoordenada(2, vertices[0].getCoordenada(2) + ajustez);

                    // Segundo Vertice
                    vertices[1].setCoordenada(0, vertices[1].getCoordenada(0) + ajustex);
                    vertices[1].setCoordenada(1, vertices[1].getCoordenada(1) + ajustey);
                    vertices[1].setCoordenada(2, vertices[1].getCoordenada(2) + ajustez);

                    // Terceiro Vertice
                    vertices[2].setCoordenada(0, vertices[2].getCoordenada(0) + ajustex);
                    vertices[2].setCoordenada(1, vertices[2].getCoordenada(1) + ajustey);
                    vertices[2].setCoordenada(2, vertices[2].getCoordenada(2) + ajustez);

                    // Normal do Triangulo
                    normal = malha[cont].getNormal();
                    normal[0] += ajustex;
                    normal[0] += ajustey;
                    normal[0] += ajustez;

                    malha[cont].setNormal(normal);
                    malha[cont].setVertice(vertices);

                }
            }

            // Retorna Geometria do tipo STL
            float[] min = new float[3];
            min[0] = (float) xmin+ajustex;
            min[1] = (float) ymin+ajustey;
            min[2] = (float) zmin+ajustez;

            float[] max = new float[3];
            max[0] = (float) xmax+ajustex;
            max[1] = (float) ymax+ajustey;
            max[2] = (float) zmax+ajustez;


            return new Geometria(0, "Geometria STL", null, new STL(malha, caminho, id, min, max, i), malha);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Transforma uma ByteArray em um Integer
     * @param b byte[]
     * @return int
     */
    private int byteArrayToInt(byte[] b) {
        int value = 0;
        for (int i = 0; i < 4; i++) {
            int shift = (i) * 8;
            value += (b[i] & 0x000000FF) << shift;
        }
        return value;
    }

    /**
     * Transforma uma ByteArray em um Float
     * @param b byte[]
     * @return float
     */
    private float byteArrayToFloat(byte[] b) {
        float flt;
        int value = 0;
        for (int i = 0; i < 4; i++) {
            int shift = (i) * 8;
            value += (b[i] & 0x000000FF) << shift;
        }
        flt = Float.intBitsToFloat(value);
        return flt;
    }
}
